Amazon API Gateway は何をしてるのか
アプリケーションをユーザに公開する場合, それがGUIであってもCUIであってもインタフェースが必要になります.
Webアプリケーションを公開する場合にはWeb APIを利用するのが一般的であり, AWSもAPIをフルマネージドで活用するためのAPI Gatewayを提供しています.
非常に簡単に活用できるのですが細かい機能などを今一度洗い直す機会があればと思っており, 社内勉強会の機会があったのでAPI Gatewayについて話しました.
今回の記事では社内向け勉強会で登壇した内容をブログ向けに再編しています.
資料はSpeakerDeckで公開していますが, 内容についてより細かくこのブログで説明しますので, 是非ご閲覧ください.
What is API
まず最初にAPIが何かを確認します.
大雑把に伝えるとアプリケーションが呼び出せば予期した結果を返されるような仕組みです.
名前にある通りインタフェースです. なので呼び出し元は裏側の実装を知っている必要はありません.
私たちがエアコンを利用する時にボタンを押した後に何が作動しているか気にするでしょうか.
私たちはただあるボタンを押したら冷たい風が吹いてくることだけを知っていれば問題ありません.
コンプレッサーが動作して...など私たちは細かい仕組みを知っている必要はありません.
アプリケーションでも同様です.
APIの呼び出し元は必要な入力を与えれば出力が返ってくることだけを知っていれば良いのです.
これがインタフェースのざっくりとした説明です.
APIはApplication Programming Interfaceの略語ですので, 名前の通りアプリケーションで利用できるインタフェースです.
なのでWeb APIは大凡HTTPを利用して入力と出力をやり取りするためのインタフェースになります.
ややこしい話になってきましたがGitHubやBacklogなどのAPIを普段活用しているのではないでしょうか.
GitHubのAPIを呼び出す時, 私たちはドキュメントを読んでどのようなリクエストを投げれば欲しいレスポンスが得られるかを確認しますね. 最も利用しているAWS Servicesの機能もWeb API経由で実行していますね.
What is Amazon API Gateway
Web APIについて理解したところでAPI Gatewayについて話します. あとで機能については伝えるのでいったんは概要を掴むくらいの気持ちで流し聞してください.
Amazon API Gatewayは規模にかかわらず, 簡単にAPIの作成と保護, そして公開, モニタリングが可能なフルマネージドサービスです.
AWS のドキュメントを参考に書いた内容ですが, 大まかにAPI Gatewayについて理解できたのではないでしょうか.
さて概要を掴んだところでAPIを利用したアーキテクチャの構成図をAWSでみていきましょう.
バックエンドのLambdaとDynamoDBはいったん置いといて, API サーバの部分がAPI Gatewayに置き換わっていますね.
名前の通りAPIはインタフェースです. クライアントの視点で考えるとAPIが提供するエンドポイントの特定パスに必要な入力を渡せば必要な結果が返ってきます.
バックエンド, この図でいくところのLambda関数も同様に処理をするために必要な入力を受け取れば, やるべき処理を行い, 必要な出力を返してくれます.
クライアントはバックエンドがLambda関数であろうが, コンテナであろうがオンプレミスであろうが, 例え人力で返していようがそこに関心はありません. API Gatewayが提供するエンドポイントにリクエストを投げればレスポンスが返ってくることだけを知っていれば良いのです.
バックエンドも同様です. クライアントが誰であろうと, プログラムであろうがブラウザであろうが, 逆立ちした人間であろうが, 猫が起こした奇跡的なリクエストであろうが, そこに関心はありません. 予期したリクエストがきたら処理をして, レスポンスをAPI Gatewayに返すことだけに関心を持っています.
クライアントとバックエンドの関心は分離しており, そこのつなぎ目部分がAPI Gatewayになります.
なのでAPI Gatewayを触っていく中で常に意識の片隅においておいて欲しいことは, API Gatewayの作成はアプリケーションの構築ではなく, クライアントに対する入力と出力, そしてバックエンドに対する入力と出力を決定する抽象概念を定義していることを念頭におくと多分すっきりいきます.
長々と書いてきたのでいったん整理しましょう.
API Gateway は簡単にAPIを作成し公開できるサービスでかつ, APIに求められる様々な機能をカバーしています.
そしてAPI Gatewayはクライアントからリクエストを受け取ってそれをバックエンドに渡す, バックエンドからレスポンスを受け取ってクライアントに返す, プロキシのような働きをしています.
Basic Components of REST API
先ほどの話のおさらいをしましょう.
REST APIはProxyとしてクライアントからリクエストを受け取り, バックエンドに渡す. バックエンドからレスポンスを受け取り, クライアントに返します.
言葉にすると逆にこんがらがりますね. 問題ありません. 今からリクエストとレスポンスをやりとりする間でREST API ではどのような経路を辿っているかをみていきます.
今表示している図は実際にAPI Gatewayにリクエストが届いてからAPI Gatewayでデータをやりとりする部分になります.
左側と右側の四角がクライアントとバックエンドです.
それでは具体的に何をしているかをみていきます. たくさん内容があるのでまずはフローを追っていきます. 詳細な機能は後ほどお伝えします.
Method Request
まずAPIにリクエストが到達して, 処理が走った際にMethod Requestに到達します.
ここでは受け付けることができるクエリパラメータや必須のパラメータ, 認可の有無, API Keyの有無... などと実際に受け付けるリクエストを絞り込む, いわばリクエストの受付のような役割を担います.
たとえたくさんのリクエストがきたとしても, 10万件のリクエストがクライアントからきたとしても10万件全てが正常なリクエストでしょうか.
推測でしかないですが, 数万件のリクエストはバリデーションにかければバックエンドを実行して確認する必要もなくレスポンスを返せるような, 誤ったリクエストでしょう.
必須のパラメータを含んでいるかを確認することでバックエンドのリソースを無駄に呼び出さずレスポンスを返すことが可能になります. なので誤ったリクエストでバックエンドを無駄に起動しないためにも, レスポンスを少しでも高速に返す意味でも, Method Requestでのバリデーションは必要になります.
繰り返しにはなりますが, Method Requestはリクエスト処理の起点です. リクエストを受け取って検証を行う, 受付の役割を果たします.
そして次にリクエストが渡される先はIntegration Requestになります.
Integration Request
Integration Requestはどのバックエンドにどのようにデータを渡すかを決定します. バックエンドとは例えばLambda関数やAWSサービスなどといった実際にリクエストを処理するコンポーネントのことです.
冒頭でも話した通りAPI Gatewayは実際のアプリケーションロジックには関与していません. 実際に処理をするのは他のサービスに任せることになります.
そのためIntegration Requestはリクエストをバックエンドに対してHTTPリクエストとして渡す, 非常に重要な役割をになっています.
そのためバックエンドと接続するための情報を, Lambda関数であればARNを, SQSであればARNとIAM Roleの紐付け, HTTPであればエンドポイントを設定していきます.
またバックエンドにリクエストを投げる前にデータの整形が必要な場合, 例えばXML形式で受け取ったリクエストをJSON形式にしたいケースや, バックエンドでは不要なフィールドを削除したいといったケースに対応できるマッピングテンプレートがあります.
Integration Requestで重要なことはバックエンドに対してリクエストを渡すことと, 必要に応じてデータの整形ができることになります.
Integration Response
バックエンドから返ってきたレスポンスに関する設定を行います. 例えばHTTPステータスコードをマッピングしたり, レスポンスの内容の変換を行ったりします.
後述しますが, Integration Requestで非プロキシ統合をした場合にのみ設定可能です.
Method Response
クライアントから受け取ったリクエストに対するAPI Gatewayの最終的なレスポンスをここで定義します. ステータスコードやHTTPヘッダなどといった部分です.
Resources / Methods
REST APIの内部でどのようにリクエストがやりとりされてレスポンスとして返っていくかの概要はつかめましたでしょうか.
クライアントからリクエストが届き, クライアントにレスポンスを届くまでの流れを今まで紹介してきました.
ここで1つ疑問があがります. REST API は単一のHTTPメソッドだけを受け付けて, 単一のバックエンドにしかリクエストを渡せないのでしょうか.
例えばメンバー一覧を確認するAPIとメンバーを追加するためのAPIエンドポイントは別々にする必要があるのでしょうか. 当然そんなわけはありませんね.
REST APIは様々なリクエストを受けて様々なバックエンドにリクエストを渡すことができます.
この設定をするためにResourceとMethodについて話していきます.
まずResourceは「/」から始まる木構造のコンポーネントです.
なのでこの図では,「/」を起点に「/members」,「/members/{id}」, 「/parts」といったResourceがありますね.
木構造なので「/」が親で「/members」が子, そして「/members/{id}」が孫にあたります.
つまり通常のURLのパス部分を「/」起点に名前を持ち, パスレベルで作成して設定をかけることができるのがResourceです.
そしてResourceには複数のMethodを紐づけることができます.
名前の通りGETやPOSTといった7つのHTTPメソッドと, どのHTTPメソッドでも受け入れるANYをサポートしています.
つまりResource(パス)に対してどういったHTTPメソッドを受け入れるかを指定して幾らかの設定を加えられるのがMethodです.
最後にMethodには実際にどのようにリクエストを処理するかの設定, つまりMethod RequestとResponse, Integration RequestとResponseを定義します.
これでAPIエンドポイントで複数のパスに複数のHTTPメソッドで異なる処理をできるようになりました.
なんとなくResourcesとMethodsがわかりましたでしょうか. シンプルに考えれば「リクエストを受け付けるパス」と「HTTPメソッド」を紐づけて, Method Request以降の実際の処理を包括するのがResourcesとMethodsになります.
Integration
名前の通りバックエンドとの統合をするための設定, つまりAPI Gatewayから実際に処理をするアプリケーションへ対する繋ぎ込みにあたります.
現在のREST APIでは下記のようなバックエンドを利用できます.
- AWS Service Step Functions や SQS などといったAWSサービスを呼び出します. ですのでIntegration単位でIAM Roleを付与してバックエンドの呼び出しを制御します. これらのサービスは当然サービスごとにAPIエンドポイントを保持しておりクライアントが実行権限を保持していれば実行できます. ですが, 独自でAPIエンドポイントを公開したい場合やクライアント側で権限を保持せずにAPI Gateway側に権限を設定してリクエストを投げやすくしたいなど様々なユースケースで役に立ちます.
- HTTP HTTP, またはHTTPS通信ができるエンドポイントに対してAPI Gatewayからリクエストを投げます. パブリックに到達可能なエンドポイントであれば任意のURLで, 任意のHTTPメソッドを利用可能です.
- Lambda API Gatewayのユースケースとして真っ先に上がるのがLambdaとの統合ではないでしょうか. Lambda 関数にリクエストを投げます. 対象のLambda関数は別リージョンであっても別アカウントであっても呼び出し可能です.
- Mock モックデータ, つまりAPI Gatewayに届いたリクエストはバックエンドに渡されることなく, そのまま統合レスポンスに流れていきます. API Gatewayの機能をテストしたい場合や, バックエンドは完成していないけど, クライアントからの呼び出しを早くテストしたい場合などにモックをおきます.
- VPC Endpoint REST APIと同一リージョンのVPCにあるAWSサービスに対してリクエストを投げます. 1点だけ注意しなければいけない事項として, サービスへのルーティングにNLBが必要になります.
Lambda Integration
Lambda関数との統合はユースケースとして多いと思います.
API GatewayからLambda関数を呼び出す際は基本的にLambda関数を同期的に呼び出します.
なのでLambda関数から返ってくるレスポンスはLambda関数の実行が成功したか失敗したかになります
そしてREST APIでのLambdaとの統合は2種類あります. 1つ目がProxy Integrationで2つ目がDirect Integrationになります.
Proxy Integrationの場合はクライアントからのリクエストをAPI Gatewayがリクエストヘッダやクエリ文字列, APIに関する情報などのメタデータでラッピングします.
なのでLambda関数はクライアントが渡したペイロード, REST APIが加えた情報を元にイベントを処理できます.
なのでLambda関数は受け取ったペイロードからクライアントからのリクエストに加え呼び出し元のREST APIに関する情報も取得できます.
その代わりにですがMapping TemplateをIntegration Requestで設定したり, Integration Responseの設定ができなくなります.
おおよそ推奨しているのはこちらのProxyを利用したパターンになります.
基本的にはLambda関数をバックエンド利用する場合はProxy Integrationを利用します.
Direct Integrationの場合は前にお伝えしたようにMethod RequestからIntegration Requestを通じてバックエンドに, そしてIntegration ResponseからMethod Responseを経由してクライアントに流れるフローをそのまま活用できます.
Proxy Integrationとは異なりIntegration RequestとResponseの設定ができる, つまりマッピングなどの機能を活用できますが, Proxyと異なりマッピングの設定を自前でする必要になります.
Proxy Integrationが他のバックエンドでも利用できたら便利ですよね.
実はHTTPとVPCリンクがバックエンドの場合はProxy Integrationが利用可能です.
Lambda関数の時と同様にバックエンドに対してリクエストを渡して, レスポンスをクライアントに直接返すため, マッピングテンプレートとIntegration Responseは設定ができなくなります.
Lambda Resource Policy
API Gatewayからバックエンドを呼び出す際にはリクエスト先とIAM Roleでの権限付与が基本になります.
ですがLambda関数においては, API Gatewayから呼び出す際の権限管理をLambda側で実装可能です. Lambdaはリソースポリシーを設定できるので, つまりAPI Gatewayからの呼び出しをここで設定できます.
最も基本的なパターンでは, 条件句でArnLikeを利用してAPI GatewayのARNを指定します.
REST APIのARNを指定する場合, ARNの後ろにstage, method, resourceといった順序でどのパスへ対するどのHTTPリクエストでLambda関数を呼び出すかを制御します.
該当APIからのリクエストであればどうであれ発火を許可したい場合はアスタリスクを利用してリソースポリシーを書きます.
この場合だとどのステージでも, どのメソッドでも, どのリソースであっても許可します.
Lambda Authorizerを利用する場合であってもLambdaのリソースポリシーで制御できます. この場合のパスは「authorizers」から始まりAuthorizer名を指定します.
Mapping Template
API Gatewayでは受け取ったリクエストとレスポンスをクライアントから受け取ったリクエストバックエンドに渡す前にIntegration Requestで, クライアントに返す前にIntegration Responseで整形することができます. その際にどのように整形を行うのかは, Velocity Language Template(VTL)とJSONPathを利用して定義します.
例えばクライアントからのリクエストが左側のようなものだったとしても, VTLを利用して右側のようなリクエストに変換してからバックエンドに渡すことが可能になります.
苗字と名前が別々に入ってきたもの氏名として一括りにしたり, 茄子より肉を好むはずなので値を書き換えたりできます.
今までのフローでどのように組み込まれるかをみていきましょう. 図中に示したように今回はXMLで受け取ったリクエストをJSONに, JSONで受け取ったレスポンスをXMLに書き換えています.
今までのフローに組み込むと図のようなフローになっています. 図中だとXMLで受け取ったリクエストをJSONに変換し, JSONで受け取ったレスポンスをXMLに書き換えてからクライアントに返すようにしています.
Integration RequestとResponse部分でMapping Templateが実行されていることが分かりますね.
Stages
API Gatewayのコンポーネントを紹介してきて, クライアントからのデータ受け取りも内部処理の部分もバックエンドとのつながりもみてきました.
実は説明してきた内容を設定するだけではAPI Gatewayをクライアントから呼び出すことはできません.
REST APIではStageを作成して, StageにDeployを行うことでクライアントからAPIを呼び出すことが可能になります.
それではStageとは何でしょうか. Stageを一言で言えば, APIへ対する論理的な参照方法です.
例えば1つのAPIでv1とv2といったバージョンを持っている場合, stageはそれらに対応してv1とv2といった形になります.
API Gatewayの場合はメソッド単位でバックエンドを含む設定を持っています. v1とv2でリクエストを流すバックエンドは同一でしょうか. 大抵の場合は異なるでしょう.
ですがメソッドは単一のバックエンドしか持ちません.
Stageでは同一のエンドポイントだが論理的には分かれているコンポーネントを分離できます.
単純に考えれば, Resourceで「v1/」と「/v2」を作成してMethodを定義していけば実現できそうに見えますが, Stageではキャッシュやスロットリング, WAFの設定, ロギング, SDK, リリース設定...など様々なAPIにとって重要な設定をも含んでいるので論理的に分離したのであればStageで分けるのが良いです.
そしてREST APIはエンドポイントの一番頭のパスにStage名が入ります. v1の場合であれば「/v1」から始まり, v2の場合は「/v2」といった具体で作成されます.
Deploy
そしてAPIの設定を更新したい場合はDeploymentを作成しStageに対してデプロイします.
デプロイはその段階のAPIの設定状態です. つまりどのResourceを保持していて, Integrationの設定はこうなっていて, みたいな内容を保持しています.
1つのStageに対して複数のDeploymentを作成することはできますが, Stageが参照できるDeployは1つのみになります.
またStageはDeploymentの履歴を持ちますので, 過去の状態に戻したいなどの用件にも対応が可能です.
ここまででREST APIの基本的なコンポーネントを総ざらいしてきましたが, 大事な点を見落としていますね.
そうですエンドポイントの設定です.
Endpoint Type
今まで様々なコンポーネントをみてきましたが, クライアントからのリクエストをどこで受けるかをまだみていません.
REST APIでは3つの種別のエンドポイントを提供しており用件に応じて選択する必要があります.
Edgeを選択した場合はCloudFront Distributionを経由してREST APIにリクエストが届きます.
エッジを通過するのでリージョンを跨いだリクエストであってもある程度のレイテンシでリクエストを受け取れます.
Regionを選択した場合はクライアントから直接REST APIにリクエストが届きます.
そのため同一リージョンからのリクエストであればEdgeよりレイテンシが低くなることがあります. またCloudFrontと組み合わせることも可能です.
VPCを選択した場合はパブリックなエンドポイントをもたずにVPC 内からのアクセス, Private Linkを経由したアクセスのみ可能になります.
どれを利用するかはケースによりますが, クライアントの性質をみてEdgeかRegionを選択するのが基本になります.
Release it !
最後に今までのまとめも含めてデプロイのフローをみていきます.
まずはAPIを設計していきます. ここはAPI Gateway固有の作業ではなくAPIを作成する場合には必要になる作業に該当します.
次にプロトコルとしてHTTPを利用するか, Websocketを利用するかを選択します.
今まで話題には出していませんが, API GatewayではHTTPとWebsocketが利用可能です.
ここまでで大枠の設定が完了したのでResourcesやMethodsなどAPI Gatewayで行う処理を定義していきます.
そしてリクエストの受け入れが決まったので最後にバックエンドと繋ぎ込んで, Stageに対してデプロイが可能になります.
大まかではありますが, 大体REST APIの構築はこのような順序で行われていくかと思います.
Protect API
今までREST APIの主要なコンポーネントをみてきました. ここからは少しづつ細かい内容に入っていきます.
まず初めはAPIの保護です.
例えば指定した地域の現在の天気を返すAPIがあったとします. このAPIは公に公開されていても, また保持しているデータをAPI経由で誰もが取得しても問題はありません.
ですがユーザの情報を返したり注文をリクエストするようなAPIがあった場合は適切に保護する必要があります.
REST APIではMethod RequestのタイミングでAPIへ対するアクセスが許可されているかを判断できます.
バックエンドで権限の検証を含めて行うことも可能ですが, 機能として分離した方が開発や保守が容易になりやすいでしょう. つまりメリットが大きいので必要に応じて利用しましょう.
1つ目のパターンとして, APIを保護する必要がない場合はOpenを選択します. これは誰もがAPIへのアクセスを許可されている状態です.
2つ目のパターンとして, IAMを利用した制御が挙げられます. IAM UserやRoleで発行されるアクセスキーを利用してAPI GatewayはAPIへのアクセスを検証します.
該当のAPI Gatewayの呼び出し権限をIAM UserやRoleにアタッチされている場合でかつ, アクセスキーが正当なものである場合はバックエンドの呼び出しを許可します.
3つ目のパターンとしてCognito User Poolと接続して, 発行されたOAuthトークンの検証をAPI Gatewayで行い, バックエンドの呼び出すを制御できます.
最後のパターンがLambda Authorizerを利用してBearer TokenなどをLambda関数に渡してAPI呼び出し権限の制御を行うことができます.
Lambda Authorizer
Lambda Authorizerを利用する場合はBearer Tokenやリクエストパラメータを受け取り, アクセス権限の確認をするLambda関数で検証します.
この時利用するLambda関数は通常通り同期的にLambda関数を呼び出し, ペイロードとしてAPI GatewayからBearer Tokenなどが渡されます.
図中には2つのLambda関数があり, 下の方のLambda関数がLambda Authorizerで呼び出される対象です.
クライアントからアクセストークンを含むリクエストがAPI Gatewayに届いたとしましょう.
API Gatewayはバックエンドの呼び出しをする前にまずは, アクセストークンの検証を行います.
検証を行うLambda関数はアクセストークンを検証してレスポンスとして, バックエンドを呼び出して良いかを記載したポリシードキュメントを返します.
API Gatewayはレスポンスとして受け取ったポリシードキュメントの内容を元にバックエンドにレスポンスを渡して処理を継続するか, それともクライアントにアクセス拒否を示すかを決定します.
Bearer Tokenを利用してJWTが渡されるパターンをサンプルに, どのようなリクエストがLambda関数に渡されて, どのようなレスポンスが返されるかをみていきます.
まず入力は当然JWTになります. JWTはエンコードされているのでデコードして, アクセストークンの検証を行っていきます.
検証方法でどのようなことを行うかについてはこちらのブログをご参照ください.
トークンの検証が成功してかつ, クライアントに許可されているスコープとAPI呼び出しを照らし合わせて問題なければLambda関数はポリシードキュメントを返します.
中身は図に記載している通り, API呼び出しを許可するかどうかを記載します.
もしアクセストークンの検証に失敗した場合にはDeny Effectを保持したポリシードキュメントを返します. この結果を受けてREST APIはバックエンドを呼び出さずにそのままクライアントにリクエストが失敗したとレスポンスを返します.
SPA Architecture
SPAを利用したアーキテクチャでOAuthを利用したAPI呼び出しの制御を考えてみます.
このケースではWebブラウザがOAuthクライアントに該当し, 多くの場合でブラウザを利用するユーザがリソースオーナになるケースが多いかと思います.
なのでAPIを呼び出す前にまずはOAuthクライアント(ブラウザ)は認可サーバとやりとりをしてアクセストークンを発行します. 今回はSPAでかつImplict Grant Flowを利用しての発行を想定します.
クライアントがアクセストークンを発行したらAPIを呼び出す際に, アクセストークンを含めてリクエストを行います.
API Gatewayでリクエストを受け取った後にLambda Authorizerでアクセストークンの検証を行い, ポリシードキュメントを返します.
これがSPAの場合でのAPI保護方法の一例になります.
Regular Architecture
次のパターンとしてAPIとは別にWebサーバがあり, WebサーバがクライアントとAPIとのやりとりをするパターンをみていきます.
今回はアクセストークンの発行をCode Grant Flowで行うことを想定します.
Implicit Grant Flowと異なりバックエンドでアクセストークンを保持し, APIの呼び出しを行います.
リクエスト元は大きく変わりました. ですがAPI側での処理は大きく変わっていないことに注目してみてください.
Manage REST API
REST API に関する機能と利用方法はあらかた確認できました. REST APIはどのように管理すべきでしょうか.
端的に言えば他のAWSリソースと同様に設定をコードに落とし込んで管理できるのが一番でしょう.
AWSやサードパーティツールなど様々な方法でREST APIを管理することができ, CDKやCLI, CloudFormation, Terraform, SAM, Serverless Frameworkなど多種多様なツールがあります.
どのツールを選択するかは好みの問題と解決したい課題に適切なものを選択すれば良いかと思います.
OpenAPI
APIはインタフェースなので抽象的です. 今までも見てきたようにREST APIではリクエストやレスポンスで様々検証や整形は行えど, 実際の処理は担っていません.
冒頭でもお伝えしたようにクライアントはAPIに対して「必要な入力」を与えれば「予期された出力」が返ってきます.
バックエンドのロジックを知っている必要はありません. 関心はAPIの入力と出力にあります.
バックエンドも同様です.
API Endpointのどのパスにリクエストが投げられたかは関心の範囲ではなく, バックエンドに対する入力と予期されている出力に関心があります.
REST APIの設計をしてからドキュメント化, つまりクライアントがどのパスにどの入力を渡すとどんな出力が返ってくるかを一元的に管理できたら便利ですよね? さらにREST APIの設計を書いたらクライアントが参照するドキュメントが生成されたら楽ですよね.
APIはインタフェースだからこそクライアントに対するレスポンス, 反対にバックエンドはクライアントから受け取るリクエストと必要なレスポンスを, これを元に開発ができればクライアントの制作もバックエンドの制作も楽になるでしょう.
これを実現するためにOpen API Specification(OAS)というREST APIを定義するための標準仕様があります. yamlかjsonでかけます. GUIで書きたい人はStoplight Studioがおすすめです.
標準仕様なのでAPI GatewayのみならずGoogle Cloud Endpointsなどでも利用可能です.
話は逸れますがOASは元々Swagger Specificationでした.
歴史的経緯でOASになり, OASに関連するツール, 例えばエディタとしてSwagger UI, ドキュメントを生成するSwagger UI, コードの自動生成を行うSwagger CodegenなどのことをSwaggerと今はさすようになっています.
API Gateway Extensions to OpenAPI
OASは標準仕様なのでAPI Gatewayのことは考慮されて作成されていません.
なのでそのままだと定義をインポートした後にバックエンドのLambda関数と統合したりLambda Authorizerを設定したり...大変ですね.
API GatewayはOASを拡張しているのでOASだけでLambda Proxy IntegrationやLambda Authorizerなど様々な機能を合わせて定義できます.
キーが「x-amazon-apigateway」から始まる拡張定義があるのでこれを活用することでより便利にAPIをimportできます.
またOASで定義した仕様をAPI Gatewayにインポートすることができます.
1点だけ注意が必要なのですが, インポートしただけではデプロイまで完了していません.
なのでデプロイをしてクライアントからアクセスできるようにしましょう. 逆に既存のREST APIからOASを出力することも可能です.
Token Bucket
API Gatewayではスロットリングを設定できます. スロットリングの方法を話す前にまずは, API Gateway でリクエストを受ける際の仕組みについて話していきます.
API Gatewayではアカウント単位で1秒あたり10,000件のリクエストを上限としてリクエストを受け入れることができます. そして5,000のバーストを受け入れることができます.
例えば 1msecの間に 5,000件のリクエストがAPI Gatewayに飛んできたとします. API Gatewayは問題なくリクエストの処理を行います.
ですが 1msecの間に 5,001件のリクエストがAPI Gatewayに飛んできたとします. 最初の5,000件のリクエストは処理しますが最後の1件は429レスポンス, つまりリクエストが多すぎて処理ができないとクライアントに伝えます.
今度の例では最初の1msecの間に5,000件のリクエストがAPI Gatewayに届き, 1msec後でかつ最初のリクエストから1秒の間のどこかで5,000件のリクエストが届いたとします. この時API Gatewayはリクエストを処理できます.
このアルゴリズムはトークンバケットと呼ばれ, バケットの中にあるトークンの数だけリクエストを消化できる仕組みです. そして1つのリクエストで1つのトークンが消費されます. API Gatewayはデフォルトで最大5,000のトークンをバケットに保持できて, この値をバーストと呼びます. またAWSからは毎秒10,000のトークンが補充されていきます.
少し奇妙に見えた先ほどの3つの例がなぜこのように動作しているか分かりましたでしょうか.
とても短い時間で受けられるリクエストは5,000件ですが1秒間であれば補充される合計値である10,000がリクエストの上限になります.
これはソフトリミットなのでAWSのサポートケース経由で上限を緩和することができます.
アカウント単位でのスロットリングをみたので次はAPI単位などより細かいレベルでの制御をみていきます.
Throttling
先ほども話したようにAPI Gatewayのスロットリングはアカウント単位で共有されます.
なので場合によっては1つのAPIによってトークンが使い切られ, 他のAPIでリクエストを返すことができない...といった状態になる可能性があります.
API単位でスロットリングを制御できれば便利ですよね.
API GatewayではStage単位でデフォルトのスロットリングを指定できます.
より詳細に伝えるとStage単位でMethodのデフォルトのレートとバーストが設定され, Methodsはそれにならってスロットリングを行います.
何も変更しなければデフォルト値ですがレートとバースト共に設定が可能です.
より細かい制御を行いたい場合はData Usage Planを利用することでクライアントレベルでの制御が可能になります.
これはクライアントに対してAPI Keyを発行することでクライアントからのリクエストメトリクスを取得してAPIへ対してアクセスできる量と速度を設定する, つまりスロットリングの設定を行います.
そしてUsage Planを利用した場合は, リソースに対するPOSTメソッドはスロットリングを厳し目に設定して, GETメソッドはある程度緩めに...といったメソッドレベルでの制御が可能です.
Monitoring / Tracing
APIのモニタリングは実際にクライアントからリクエストを受け付けてレスポンスを返すところになります.
なのでビジネス的にも最もメトリクスを注意深く見る必要があり, API Gatewayは様々なメトリクスをCloudWatch経由で提供しています.
例えばシステムが正しく動作しない, これは何を根拠に考えるべきでしょうか.
バックエンドが完全に応答不能になっており, 何件リクエストを投げても返って来ない場合は確かに異常です.
ですがたまたま1, 2件応答できずにAPI Gateway経由で5xxエラーを返した時これははたしてサービス異常とみなすべきでしょうか. もちろんサービスに依存するので正確な回答はありません.
例えばバックエンドからAPI経由でクライアントにレスポンスを返せたがレイテンシが10秒かかった時, サービスは正常に稼働していると, 利用者に価値を提供できていると言えるでしょうか.
サービスの性質を考えて目標を定めて, メトリクスを確認するのが良いですね. そしてそのためにAPI Gatewayは十分なCloudWatch Metrics, APIの実行とアクセスに関するログを取得できます.
またX-Rayを利用すれば, リクエストのトレーシングが可能でどこでレイテンシが発生しているかを分析することができます.
DynamoDBやLambdaなどとも統合しているのでサービスごとに設定することで容易に分散トレーシングを実現できます.
Cache
API の応答速度はサービスの品質のために非常に重要です.
エンドポイントのロケーション設定やバックエンドの改善も非常に重要ではありますが, エンドポイントがレスポンスをキャッシュしてクライアントにキャッシュ結果を返せたらどうでしょうか.
処理の大半がスキップされバックエンドの処理も不要になるためレスポンスが高速になりますね.
REST APIはエンドポイントでのキャッシュをするために専用のキャッシュインスタンスを作成し, リクエストを保存していきます.
キャッシュインスタンスにはどれだけ保持できるかのサイズを指定した上で秒単位で指定した有効期限が切れるまでレスポンスをキャッシュします.
またAPI Gatewayは基本的にリクエスト単位での従量課金ですが, キャッシュのみ時間単位でキャッシュサイズに応じた課金が発生します.
キャッシュを有効にする, つまりキャッシュインスタンスの作成はStage単位での作成が行われデフォルトではGETメソッドのみキャッシュされます.
キャッシュインスタンスのサイズ変更を行う場合インスタンスの再作成が発生します. つまりその時点でキャッシュインスタンスに含まれていた内容は破棄されます.
またStage単位でのキャッシュでは十分な制御を行えない場合, Method単位で設定を上書きしてレスポンスをキャッシュするかしないかの設定も可能です.
Security
APIへ対するSQLiやXSSやDDoSなど様々な攻撃があり得ます. APIの保護にWAFが利用できたらさぞ便利でしょう.
AWS WAFがサービスとしてあり, REST APIのStageに対してWeb ACLを紐づけることが可能です. そしてWAFでのリクエスト評価はMethod Requestでの各種評価やResource Policy, IAM Policyなどでの評価より前に行われます.
Canary release
APIのデプロイを一気に行わずに一部の先行ユーザにのみ, トラフィックの5%だけを新しいDeploymentに流し込んで確認することができます.
一般にカナリアリリースと呼ばれる手法で実際の本番環境へデプロイしてエラーが起きていないかを確認しつつ, 問題なければデプロイを続行する仕組みです.
REST APIではStage単位でカナリアリリースの設定が可能です.
HTTP API
最後に昨年末発表された, シンプルで安価で高速なHTTP APIについて見ていきます.
HTTP APIはより高速でより低コストで, よりシンプルにAPIを構築できるサービスです.
ほとんどのケースでHTTP APIはREST APIよりレイテンシを最大で60%削減します. APIのレスポンスが速いことはWebサイトに重要なことですよね.
その結果としてHTTP APIを経由するためにかかるオーバヘッドが99%のリクエストで10 msec以下に抑えられます.
またHTTP APIは REST APIと比較して少なくとも71%のコスト削減に役立ちます. リクエストの従量課金で元々かなり安価なREST APIでしたがそれ以上に安くなります.
それではREST APIと比較して何が大きく異なるのでしょうか. HTTP APIはとにかくシンプルです. そのため機能がかなり限定されています. もちろんAPIを構築するために十分で強力な機能を備えていますが, より機能を求める場合にはREST APIが推奨されます.
REST APIを理解したあとであれば比較的簡単にHTTP APIを理解できます. それではコンポーネントを追っていきましょう.
Routes
Routeは「/」から始まる木構造で, RouteにはそれぞれHTTPメソッドの2つで構成されています.
Routeの考え方はREST APIのResourceとMethodににています. 大きく異なるのがパスとメソッドを1つで保持します.
そして受け取ったAPIリクエストをIntegrationの設定を利用して直接バックエンドにリクエストを投げて, クライアントに対してレスポンスを返します.
つまりMethod RequestやResponseなどがすっぱりとなくなっています.
Integrations
Routesにアタッチするどのバックエンドに接続するかの設定になります.
バックエンドの対象としてHTTP エンドポイント, Lambda関数, そしてALBとNLB, Cloud Mapを経由したVPC内リソースへの接続が可能です.
HTTP エンドポイントの場合はIntegrationsが投げるエンドポイントのURLとメソッドを指定します. Lambda関数の場合はARNと渡すペイロードのバージョンを指定します.
HTTP APIでもLambdaは特別な存在です. API Gatewayが進化すればLambdaに渡されるイベントの中身も変わります.
仮にREST APIからHTTP APIに乗り換える場合にLambda関数の変更が必須なのでしょうか.
実はHTTP APIでは従来のペイロードのフォーマットをv1とし, 新しいものをv2としてどちらかを選択することでLambda関数の発火を行います. なので問題ありませんね.
Stages
HTTP APIもREST APIと同様にStageに対してデプロイします. REST APIと異なる点としては, $default Stageを作成し, HTTP APIが提供するエンドポイント以下に直接Routesを結びつけることができます.
JWT Authorizer
HTTP APIのとても魅力的な機能として, JWT Authorizerがあります.
OAuth2.0やOIDCではトークンを発行する際には推測が困難な文字列であればトークンとして利用ができるので当然JWTも利用可能です.
JWTの場合は非対称アルゴリズムで署名されているため公開鍵を利用しての検証ができます.
つまりJWTを受け取った後に検証する流れはほとんど同じであり, Issuer URLなどクライアントによって異なる部分だけを設定すれば, JWT Authorizerを作成できます.
- クライアントからトークンを受け取る場所
- Issuer URLとしてJWTの発行元のエンドポイント
- 必要に応じてAudienceを追加しての検証
あとは作成したJWT AuthorizerをRoutesに紐づけるとクライアントからのリクエストを検証してアクセスを許可と拒否できます.
To Closely
いろいろと書いてきたのですが, どうしても全ての機能は網羅できていません. とりわけWeb Socketは触れてもいないですね.
また実際に手を動かしてデプロイすると理解が深まりますので是非試して, ドキュメントを読んで理解を深化していただければと思います.